package com.sangmo.fts.teacher.service;

import com.sangmo.boot.framework.common.errors.ServiceException;
import com.sangmo.boot.framework.common.errors.StandardErrors;
import com.sangmo.boot.framework.common.session.UserSession;
import com.sangmo.boot.framework.common.utils.DateUtils;
import com.sangmo.boot.framework.data.filter.NativeQueryBuilder;
import com.sangmo.boot.framework.data.filter.PageResult;
import com.sangmo.boot.framework.data.filter.PageSearch;
import com.sangmo.boot.framework.data.filter.ValueFilter;
import com.sangmo.boot.framework.plugin.dataview.crud.service.DelegateViewService;
import com.sangmo.boot.framework.plugin.dataview.crud.service.ViewService;
import com.sangmo.boot.framework.plugin.dataview.spi.jpa.NativeQueryBuilderFactory;
import com.sangmo.fts.basic.model.FileUpload;
import com.sangmo.fts.basic.service.FileUploadService;
import com.sangmo.fts.clazz.model.Clazz;
import com.sangmo.fts.clazz.service.ClazzService;
import com.sangmo.fts.teacher.vo.ExaminationDistributionView;
import com.sangmo.fts.teacher.interfaces.ExaminationSubmitRequest;
import com.sangmo.fts.teacher.interfaces.ExaminationEnterRequest;
import com.sangmo.fts.teacher.interfaces.ScoreSettingCollect;
import com.sangmo.fts.teacher.model.DurationRatioSetting;
import com.sangmo.fts.teacher.model.Examination;
import com.sangmo.fts.training.build.TrainingBuilder;
import com.sangmo.fts.training.model.Score;
import com.sangmo.fts.training.model.Team;
import com.sangmo.fts.training.model.Training;
import com.sangmo.fts.training.model.TrainingHistory;
import com.sangmo.fts.training.service.ScoreService;
import com.sangmo.fts.training.service.TeamService;
import com.sangmo.fts.training.service.TrainingHistoryEntityService;
import com.sangmo.fts.training.service.TrainingService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

@Service
@Transactional(rollbackFor = Exception.class)
public class ExaminationService extends DelegateViewService<Examination> {

    @Autowired
    private ExaminationEntityService examinationEntityService;

    @Autowired
    private TrainingService trainingService;

    @Autowired
    private TeamService teamService;

    @Autowired
    private ClazzService clazzService;

    @Autowired
    private ScoreSettingService scoreSettingService;

    @Autowired
    private ScoreService scoreService;

    @Autowired
    private TrainingHistoryEntityService trainingHistoryEntityService;

    @Autowired
    private FileUploadService fileUploadService;

    @Override
    protected ViewService<Examination> getEntityService() {
        return examinationEntityService;
    }

    @Override
    public PageResult<Examination> search(PageSearch ps) {
        ps.getFilters().add(new ValueFilter("createdBy", ValueFilter.OP_EQ, UserSession.getUserInterface().getId()));
        return super.search(ps);
    }

    public Training enter(Long examinationId, ExaminationEnterRequest request) {
        Examination examination = this.get(examinationId);
        Long currentUserId = (Long) UserSession.getUserInterface().getId();
        checkExamEnterAccess(examinationId, currentUserId, request.getTeamId());
        if (Examination.Modes.INDIVIDUAL.equals(examination.getMode())) {
            Training training = trainingService.findByExaminationIdAndUserId(examinationId, currentUserId);
            if (null == training) {
                Training newTraining = TrainingBuilder.newInstance()
                        .setExaminationId(examinationId)
                        .setBusinessType(examination.getBusinessType())
                        .setMode(examination.getMode())
                        .setStartTime(new Date())
                        .build();
                training = trainingService.save(newTraining);
            }
            return training;
        }
        else if (Examination.Modes.TEAM.equals(examination.getMode())) {
            teamService.checkTeamValid(request.getTeamId(), currentUserId);
            Training training = trainingService.findByExaminationAndTeamId(examinationId,request.getTeamId());
            if (null == training) {
                Training newTraining = TrainingBuilder.newInstance()
                        .setExaminationId(examinationId)
                        .setTeamId(request.getTeamId())
                        .setBusinessType(examination.getBusinessType())
                        .setMode(examination.getMode())
                        .setStartTime(new Date())
                        .build();
                training = trainingService.save(newTraining);
            }
            return training;
        }
        return null;
    }

    public FileUpload createReportDocument(Long id, Long trainingId, ExaminationSubmitRequest request) {
        Long currentUserId = (Long)UserSession.getUserInterface().getId();
        checkExamAccess(id, currentUserId);
        Training training = trainingService.get(trainingId);
        if (!id.equals(training.getExaminationId())) {
            throw new ServiceException(StandardErrors.CLIENT_ERROR.getStatus(), "error.examination.training.notmatched");
        }
        trainingService.checkAccess(training, currentUserId);
        //
        Score score = scoreService.createOneIfNotExists(training);
        score.setReportDoc(request.getFileUploadId());
        scoreService.save(score);
        return create(score.getId(), request.getFileUploadId(), "Score");
    }

    public FileUpload create(Long scoreId, Long fileUploadId, String refType) {
        FileUpload upload = fileUploadService.updateRef(fileUploadId, ""+scoreId, refType);
        return upload;
    }

    public void submit(Long id, Long trainingId) {
        Long currentUserId = (Long)UserSession.getUserInterface().getId();
        checkExamAccess(id, currentUserId);
        Training training = trainingService.get(trainingId);
        if (!id.equals(training.getExaminationId())) {
            throw new ServiceException(StandardErrors.CLIENT_ERROR.getStatus(), "error.examination.training.notmatched");
        }
        trainingService.checkSubmitAccess(training, currentUserId);
        if (Training.Modes.INDIVIDUAL.equals(training.getMode())) {
            scoreService.createOneIfNotExists(training);
        } else if (Training.Modes.TEAM.equals(training.getMode())) {
            scoreService.createTeamIfNotExists(training);
        }
        training.setEndTime(new Date());
        trainingService.save(training);
    }

    public Examination checkExamAccess(Long examinationId,Long userId) {
        Examination examination = examinationEntityService.findOneCache(examinationId);
        //校验有效考核时间范围
        Date now = new Date();
        if (now.compareTo(examination.getStartTime()) <= 0
                || now.compareTo(examination.getEndTime()) >= 0) {
            throw new ServiceException(StandardErrors.CLIENT_ERROR.getStatus(), "error.examination.endTime.expired");
        }
        //校验用户是否在考核班级内
        Collection<Clazz> clazzes = clazzService.findByExaminationId(examinationId);
        List<Long> clazzIds = clazzes.stream().map(Clazz::getId).collect(Collectors.toList());
        Clazz userClazz = clazzService.findByStudentId(userId);
        if (!clazzIds.contains(userClazz.getId())) {
            throw new ServiceException(StandardErrors.CLIENT_ERROR.getStatus(), "error.examination.enter.notallowed");
        }
        return examination;
    }

    public void checkExamEnterAccess(Long examinationId, Long userId, Long teamId) {
        Examination examination = checkExamAccess(examinationId, userId);
        //校验团队是否属于本次考核
        if (Examination.Modes.TEAM.equals(examination.getMode())) {
            if (null == teamId) {
                throw new ServiceException(StandardErrors.CLIENT_ERROR.getStatus(), "error.team.enter.teamId.notNull");
            }
            Team team = teamService.get(teamId);
            if (!examinationId.equals(team.getExaminationId())) {
                throw new ServiceException(StandardErrors.CLIENT_ERROR.getStatus(), "error.examination.enter.team.notallowed");
            }
        }
    }


    public List<ExaminationDistributionView> distribution(Long id) {
        Examination examination = this.get(id);
        if (!Examination.States.COMPLETED.equals(examination.getState())) {
            throw new ServiceException(StandardErrors.CLIENT_ERROR.getStatus(), "error.examination.state.notcompleted");
        }
        return scoreService.distributeByExaminationId(id);
    }


    public PageResult<Examination> searchForStudent(PageSearch ps) {
        Long currentUserId = (Long) UserSession.getUserInterface().getId();
        NativeQueryBuilder builder = NativeQueryBuilderFactory.create()
                .selectForm("SELECT a.* FROM examination a ")
                .where(" a.start_time <= :now AND a.end_time >= :now AND " +
                        "EXISTS (select ec.id from examination_clazz ec where ec.clazz_id IN (select clazz_id from user_clazz where user_id = :userId ) ) ")
                .where(ps)
                .putVariable("now", new Date())
                .putVariable("userId", currentUserId)
                .transform(Examination.class);
        return builder.buildPage();
    }

    public void scoreAuto() {
        Collection<Examination> examinations = examinationEntityService.findPendingExam();
        for (Examination e : examinations) {
            score(e);
            e.setState(Examination.States.COMPLETED);
            this.save(e);
        }
    }


    private void score(Examination examination) {
        Collection<Training> trainings = trainingService.findByExaminationId(examination.getId());
        ScoreSettingCollect collect = scoreSettingService.findByTeacherId(examination.getCreatedBy());
        for (Training training : trainings) {
            List<Score> scores = new ArrayList<>();
            if (Training.Modes.INDIVIDUAL.equals(training.getMode())) {
                Score score = scoreService.createOneIfNotExists(training);
                scores.add(score);
            } else if (Training.Modes.TEAM.equals(training.getMode())) {
                List<Score> scoreList = scoreService.createTeamIfNotExists(training);
                scores.addAll(scoreList);
            }
            //业务流程完成度评分
            List<TrainingHistory> ths = trainingHistoryEntityService.findDistinctByTrainingId(training.getId());
            Integer processNodeTotal = TrainingHistory.businessTypeMapProcessNodeTotal.get(training.getBusinessType());
            BigDecimal processMark =  new BigDecimal(String.valueOf(ths.size())).divide(new BigDecimal(processNodeTotal), 2).multiply(new BigDecimal("100"));

            //实训时长评分
            BigDecimal hourDiff = DateUtils.hourDiff(training.getStartTime(), training.getEndTime()==null?examination.getEndTime():training.getEndTime());
            DurationRatioSetting minDurationRatioSetting = matchingMinDurationRatioSetting(hourDiff, collect.getDurationRatioSettings());
            BigDecimal durationMark = new BigDecimal(String.valueOf(minDurationRatioSetting.getScore()));

            //资产增长率评分

            for (Score score : scores) {
                score.setProcessMark(processMark);
                score.setDurationMark(durationMark);
            }
            scoreService.saveAll(scores);
        }
    }


    private DurationRatioSetting matchingMinDurationRatioSetting(BigDecimal hourDiff, List<DurationRatioSetting> durationRatioSettings) {
        DurationRatioSetting minDurationRatioSetting = null;
        for (DurationRatioSetting setting : durationRatioSettings) {
            if (hourDiff.compareTo(new BigDecimal(setting.getMaxValue())) <= 0) {
                minDurationRatioSetting = setting;
            }
        }
        return minDurationRatioSetting;

    }


}
